<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <pre lang="java"><code>
Thread-0: 0
Thread-1: 1
Thread-2: 2
Thread-0: 3
Thread-1: 4
Thread-2: 5
....
Thread-1: 100
</code></pre>
  <h1 data-lake-id="iPKzo" id="iPKzo"><span data-lake-id="u9358f165" id="u9358f165">典型回答</span></h1>
  <p data-lake-id="u34511d37" id="u34511d37"><span data-lake-id="u0efafd15" id="u0efafd15">这个问题主要考察多线程的线程安全和通信机制，常见的处理方式有notify/synchorized和condition/ reentrantlock。但是往往有同学只注意线程安全，而忽略了通信机制，常见的错误写法如下：</span></p>
  <pre lang="java"><code>
public class Test {

	private static int i = 1;

	public static void main(String[] args) {
        for (int i = 0; i &lt; 3; i++) {
            new Thread(new Print(i)).start();
        }
    }

    private static class Print implements Runnable {

        private final int index;

        public Print(int index) {
            this.index = index;
        }

        @Override
        public void run() {
            while(true) {
                synchronized (Print.class) {
                    if (i &gt;= 101) {
                        return;
                    }
                    System.out.println("Thread-" + index + " " + i++);
                }
            }
        }
    }
}
</code></pre>
  <p data-lake-id="u1794a4c4" id="u1794a4c4"><span data-lake-id="u6f70afb6" id="u6f70afb6">这样写固然能通过锁来保证循环打印了1-100，但是却不能保证线程是按照顺序打印的，这个时候就需要用到线程的通信机制。</span></p>
  <h2 data-lake-id="kluNn" id="kluNn"><span data-lake-id="u8ce10a36" id="u8ce10a36">Synchronized</span></h2>
  <p data-lake-id="uc9f223a3" id="uc9f223a3"><span data-lake-id="udf132f0f" id="udf132f0f">我们可以结合Sync和Object#notifyAll来完成，如下所示</span></p>
  <pre lang="java"><code>
public class SortTest {

    private static final Object LOCK = new Object();
    private static volatile int count = 0;
    private static final int MAX = 100;

    public static void main(String[] args) {
        Thread thread = new Thread(new Seq(0));
        Thread thread1 = new Thread(new Seq(1));
        Thread thread2 = new Thread(new Seq(2));
        thread.start();
        thread1.start();
        thread2.start();
    }

    static class Seq implements Runnable {

        private final int index;

        public Seq(int index) {
            this.index = index;
        }

        @Override
        public void run() {
            while (count &lt; MAX) {
                synchronized (LOCK) {
                    try {
                        while (count % 3 != index) {
                            LOCK.wait();
                        }
                        if(count &lt;=MAX){
                            System.out.println("Thread-" + index + ": " + count);
                        }
                        count++;
                        LOCK.notifyAll();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
</code></pre>
  <h2 data-lake-id="dF50o" id="dF50o"><span data-lake-id="uefaa6661" id="uefaa6661">ReentrantLock</span></h2>
  <p data-lake-id="uf1ff9001" id="uf1ff9001"><span data-lake-id="u456f3291" id="u456f3291">我们可以使用ReentrantLock和Condition尝试解决这种问题。大概的解决思路就是先通过lock对资源加锁，然后通过condition指定的唤醒下一个线程。相信大家都已经发现，这种方式比Synchronized的优点就是sync只能唤醒一个线程或者全部唤醒来让大家竞争，但是通过condition我们可以唤醒指定线程，避免资源浪费</span></p>
  <pre lang="java"><code>
public class Test {
    private static final int WORKER_COUNT = 3;
    private static int countIndex = 0;
    private static final ReentrantLock LOCK = new ReentrantLock();

    public static void main(String[] args){
        final List&lt;Condition&gt; conditions = new ArrayList&lt;&gt;();
        for(int i=0; i&lt; WORKER_COUNT; i++){
            // 为每一个线程分配一个condition
            Condition condition = LOCK.newCondition();
            conditions.add(condition);
            Worker worker = new Worker(i, conditions);
            worker.start();
        }

    }

    static class Worker extends Thread{

        int index;
        List&lt;Condition&gt; conditions;

        public Worker(int index, List&lt;Condition&gt; conditions){
            super("Thread-"+index);
            this.index = index;
            this.conditions = conditions;
        }

        private void signalNext(){
            int nextIndex = (index + 1) % conditions.size();
            conditions.get(nextIndex).signal();
        }

        @Override
        public void run(){
            while(true) {
                //锁住 保证操作间同时只有一个线程
                LOCK.lock();
                try {
                    // 如果当前线程不满足打印条件，则等待
                    if (countIndex % 3 != index) {
                        conditions.get(index).await();
                    }
                    if (countIndex &gt; 100) {
                        // 唤醒下一个线程，保证程序正常退出
                        signalNext();
                        // 退出循环 线程运行结束
                        return;
                    }
                    System.out.println((this.getName() + " " + countIndex));
                    // 计数器+1
                    countIndex ++;
                    // 通知下一个干活
                    signalNext();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    LOCK.unlock();
                }
            }
        }
    }
}

</code></pre>
  <p data-lake-id="u64aeb66d" id="u64aeb66d"><span data-lake-id="uf92aa813" id="uf92aa813">注意，对于Worker里面的逻辑，不能为了图省事，用下面的写法：</span></p>
  <pre lang="java"><code>
public void run(){
    while(true) {
        //锁住 保证操作间同时只有一个线程
        LOCK.lock();
        try {
           if (countIndex &gt; 100) {
                // 唤醒下一个线程，保证程序正常退出
                signalNext();
                // 退出循环 线程运行结束
                return;
            }

            System.out.println((this.getName() + " " + countIndex));
            // 计数器+1
            countIndex ++;
            // 通知下一个干活
            signalNext();
            conditions.get(index).await();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            LOCK.unlock();
        }
    }
}
</code></pre>
  <p data-lake-id="u06cf9fa1" id="u06cf9fa1"><span data-lake-id="ub883b650" id="ub883b650">这种写法是错误的，原因是如果刚开始不让所有线程都等待，就有可能会导致线程竞争，举个例子：</span></p>
  <p data-lake-id="ua302679a" id="ua302679a"><span data-lake-id="u9c2d8f9b" id="u9c2d8f9b">step1：thread0执行逻辑</span></p>
  <p data-lake-id="uf93723fd" id="uf93723fd"><span data-lake-id="u655c52a7" id="u655c52a7">step2：thread2被锁阻塞</span></p>
  <p data-lake-id="u547e516b" id="u547e516b"><span data-lake-id="u2f8901b4" id="u2f8901b4">step3：thread0执行完成，唤醒thread1</span></p>
  <p data-lake-id="u1911ed35" id="u1911ed35"><span data-lake-id="ua2f32dc9" id="ua2f32dc9">step4：thread2和thread1竞争锁，thread2竞争成功，就会导致thread0执行完成后，直接由thread2执行</span></p>
  <h2 data-lake-id="TGKjE" id="TGKjE"><span data-lake-id="u655d7908" id="u655d7908">Thread#yield</span></h2>
  <p data-lake-id="u9372fecc" id="u9372fecc"><span data-lake-id="u10765bc3" id="u10765bc3">除了线程之间的通信之外，我们还可以使用一种取巧的方式，就是通过指定线程打印某些值，如Thread0打印0,3,9等值。</span></p>
  <p data-lake-id="u2758701f" id="u2758701f"><span data-lake-id="u709a1cdb" id="u709a1cdb">核心思想是通过yield自旋的方式，如果当前的值不需要被当前线程打印，那么就让出该线程。如下所示：</span></p>
  <pre lang="java"><code>
private static volatile int count = 0;
private static final int MAX = 100;
static class OtherWorker implements Runnable {

        private final int index;

        public OtherWorker(int index) {
            this.index = index;
        }

        @Override
        public void run() {
            while (count &lt; MAX) {
                while (count % 3 != index) {
                    Thread.yield();
                }
                if (count &gt; MAX) {
                    return;
                }
                System.out.println("Thread-" + index + " " + count);
                count++;
            }
        }
    }
</code></pre>
  <p data-lake-id="u750fea68" id="u750fea68"><span data-lake-id="u96b0fc23" id="u96b0fc23">这种方式需要线程不停的竞争和自选，性能显然比不过前两种方法。同时，因为yield并不能保证立刻让出CPU，所以这种方法是有风险的</span></p>
  <h1 data-lake-id="Pf3qK" id="Pf3qK"><span data-lake-id="uded2f4d6" id="uded2f4d6">知识扩展</span></h1>
  <p data-lake-id="ue4af1932" id="ue4af1932"><span data-lake-id="u203754b6" id="u203754b6">这种问题还有很多变种，如三个线程顺序打印ABC，三个线程按照要求顺序打印ABC等。</span></p>
  <p data-lake-id="u027a45a9" id="u027a45a9"><span data-lake-id="u7ebf6e3d" id="u7ebf6e3d">这些问题都是上线问题的变体，譬如把1-100换成ABC，常见的1-100转为ABC的代码转换如下所示：</span></p>
  <pre lang="java"><code>
for (int i = 0; i &lt; 100; i ++) {
    char ascii = (char)(65 + i%3);
}
</code></pre>
  <p data-lake-id="u13436b2a" id="u13436b2a"><span data-lake-id="ue672e1b7" id="ue672e1b7">​</span><br></p>
 </body>
</html>